//
//  WCWebFetchController.m
//  WebClipper
//
//  Created by Daniel Braun on 24/02/07.
//  Copyright 2007Daniel Braun http://page-appart.net, http://braun.daniel.free.fr  All rights reserved.
//

#import "WCWebFetchController.h"
#import "RequestExtensions.h"

#import <WebKit/WebView.h>
#import <WebKit/WebFrame.h>
#import <WebKit/WebResource.h>
#import <WebKit/WebDataSource.h>
#import <WebKit/DOM.h>
#import <WebKit/DOMExtensions.h>
#import <WebKit/WebDOMOperations.h>
#import <WebKit/WebPolicyDelegate.h>


#import "WCWebExtractSet.h"
#import "WCUrlAccess.h"
#import "NSTreeExt.h"
#import "WCWebExtractItem.h"
#import "WCWebExtractionPath.h"
#import "MyDocument.h"
#import "objcExt.h"
#import "WCExtractAction.h"
#import "WCExtractText.h"
#import "WCExtractDate.h"
#import "WCExtractUrl.h"

@implementation WCWebFetchController


+ (void)initialize {
	if (self == [WCWebFetchController class]) {
		[self exposeBinding:@"eset"];
		[self exposeBinding:@"curitem"];
		NSArray *ta=[NSArray arrayWithObjects:@"eset", nil];
		[self setKeys:ta triggerChangeNotificationsForDependentKey:@"surl"];
		[self setKeys:ta triggerChangeNotificationsForDependentKey:@"documentRoot"];

		[self setKeys:[NSArray arrayWithObject:@"selectedExtractionItem"]
			triggerChangeNotificationsForDependentKey:@"haveExtractionItem"];
		ta=[NSArray arrayWithObjects:@"eset",@"curitem", nil];
		[self setKeys:ta triggerChangeNotificationsForDependentKey:@"extractorArray"];
		[self setKeys:ta triggerChangeNotificationsForDependentKey:@"selectedAction"];
	}
}
- (id) init {
	self = [super init];
	if (self != nil) {
		editingMode=0;
	}
	return self;
}
- (void) awakeFromNib 
{
	editingMode=0;
	[webViewBrowser setDownloadDelegate:self];
	[webViewBrowser setFrameLoadDelegate:self];
	[webViewBrowser setUIDelegate:self];
	[webViewExtraction setDownloadDelegate:self];
	[webViewExtraction setFrameLoadDelegate:self];
	[webViewBrowser setPolicyDelegate:self];
	[webViewExtraction setPolicyDelegate:self];
	[webViewElement setPolicyDelegate:self];

}

- (void) dealloc {
	[sei release];
	[curitem release];
	[_eset release];
	[_documentRoot release];
	[loadCallBackTarget release];
	[loadCallBackPrivate release];
	[configuredConverters release];
	[extractionResult release];
	//[selectedConverterKey release];
	[super dealloc];
}

- (void) setEset:(WCWebExtractSet *)eset
{
	[_eset release];
	_eset=[eset retain];
	NSString *s=[_eset pageCache];
	if (!s) { 
		NSLog(@"loading eset %@\n", eset);
		[self loadPage];
	} else {
		NSURL *url=[_eset actualUrl];
		NSLog(@"cached url %@\n", url);
		[[webViewBrowser mainFrame] loadHTMLString:s baseURL:url];
		//NSURLRequest *nu=[[[webViewBrowser mainFrame]datasource] request];
		//NSLog(@"from cache, url %@\n",nu);
	}
#if 0
	WebArchive *we=[_eset pageCache];
	if (we) {
		NSLog(@"got page cache\n");
		DOMNode *dr;
		//[self willChangeValueForKey:@"documentRoot"];
		//[[webViewBrowser mainFrame] loadArchive:we];
	}
#endif
}

- (WCWebExtractSet *) eset
{
	return _eset;
}

- (NSString *) surl
{
	if (!_eset) return nil;
	return [_eset surl];
}

- (void) setSurl:(NSString *)s
{
	[self setCurrentUrl:[NSURL URLWithString:s] fromCache:NO
		 variables:nil method:@"GET" forceNew:NO];
}

- (void) setCurrentUrl:(NSURL *)nurl fromCache:(BOOL)fc  variables:(NSDictionary *)v method:(NSString *)m forceNew:(BOOL)forcenew
{
	NSAssert(_eset, @"eset should be set");
	int n=[_eset nItems];
	if (n && !fc) forcenew=YES; // have extract item, push a new extract set
	
	WCUrlAccess *ua=[_eset urlaccess];
	if (ua && ![ua canBeOverWritten]) forcenew=YES; // typ, ua is POST
	if (forcenew) {
		WCWebExtractSet *ne=[[WCWebExtractSet alloc]init];
		[ne setOfficialUrl:nurl];
		WCUrlAccess *ua=[ne urlaccess];
		NSAssert(ua, @"ua not set");
		if (!m) m=@"GET";
		[ua setMethod:m];
		[ua setVariables:v];
		NSAssert(myDocument, @"no current document");
		[myDocument addExtractionSet:ne];
		[ne release];
		return;
	}
	//[_eset setName:nil];
	if (fc) [_eset setActualUrl:nurl];
	else [_eset setOfficialUrl:nurl];
	ua=[_eset urlaccess];
	NSAssert(ua, @"ua not set");
	if (!m) m=@"GET";
	[ua setMethod:m];
	[ua setVariables:v];
	[self loadPage];
}

- (void) browsingRequest:(NSURLRequest *)r
{
	NSURL *url;
	[r autorelease]; // was retain at delayed invocation
	url=[r URL];
	BOOL fc=NO;
	NSString *sc=[url scheme];
	if ([sc isEqualToString:@"applewebdata"]) {
		fc=YES;
	}
#if 0
	NSDictionary *d=[r allHTTPHeaderFields];
	NSString *p=[url parameterString];
	NSString *s=[url fragment];
	NSString *rs=[url resourceSpecifier];
	NSData *b=[r HTTPBody];
	NSDictionary *var=[r htmlRequestVariables];

#endif
	NSString *method=[r HTTPMethod];
	NSDictionary *var=[r htmlRequestVariables];
	NSDictionary *bodyVar=[_eset bodyVariables];
	BOOL usebodyvar=NO;
	if (var) {
#if 0
		NSEnumerator *e=[var keyEnumerator];
		NSString *k;
		while ((k=[e nextObject])) {
			NSString *v=nil;
			NSString *vb=nil;
			v=[var objectForKey:k];
			vb=[bodyVar objectForKey:k];
			NSLog(@"look for var %@ val %@/%@\n", k, v, vb);
			if ([vb isEqualToString:v]) {
				NSLog(@"   this is body var\n");
				usebodyvar=YES;
				[var setValue:[NSNull null] forKey:k];
			}
		}
#else	
		NSArray *ak=[var allKeys];
		NSUInteger i, count = [ak count];
		for (i = 0; i < count; i++) {
			NSString *v=nil;
			NSString *vb=nil;
			NSString * k = [ak objectAtIndex:i];
			v=[var objectForKey:k];
			vb=[bodyVar objectForKey:k];
			if ([vb isEqualToString:v]) {
				NSLog(@"   this (%@) is body var\n", k);
				usebodyvar=YES;
				[var setValue:[NSNull null] forKey:k];
			}
		}
#endif
	}
	NSLog(@"going to url %@ (%@)\n", url, [r HTTPMethod]);
	if ([[r HTTPMethod]isEqualToString:@"POST"]) usebodyvar=YES; // we always post from a page!
	[self setCurrentUrl:url fromCache:fc  variables:var method:method forceNew:usebodyvar];
}

- (WCWebExtractionItem *) curitem
{
	return curitem;
}
- (void) setCuritem:(WCWebExtractionItem *)c
{
	if (c == curitem) return;
	[curitem release];
	curitem=[c retain];
	if (curitem) {
		[self setSelectedAction:[curitem extractAction]];
	}
}

- (IBAction) fetch:(id) sender
{
	[self loadPage];
}
- (void) loadPage
{
	NSString *content=nil;
	NSURL *_url=[_eset actualUrlOrContent:&content];
	
	if (!_url) {
		return;
	}
	if (content) {
		NSURL *u=[_eset officialUrl];
		NSLog(@"loading html content (%@), official url %@\n", content, u);
		[[webViewBrowser mainFrame] loadHTMLString:content baseURL:_url];
		return;
	}
	NSLog(@"loading %@\n", _url);
	NSMutableURLRequest *req=[NSMutableURLRequest requestWithURL:_url];
	WCUrlAccess *ua=[_eset urlaccess];
	NSAssert(ua, @"ua not set");
	WCWebExtractSet *prev=[_eset prevExtractSet];
	NSDictionary *bodyvar=[prev bodyVariables];
	req=[ua updateRequest:req withBodyVariables:bodyvar]; 
	if (webViewBrowser) {
		[[webViewBrowser mainFrame] loadRequest:req];
	}
}
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
	if ((sender==webViewBrowser) && ([webViewBrowser mainFrame]==frame)) {
		DOMNode *dr;
		[self willChangeValueForKey:@"documentRoot"];
		DOMHTMLDocument *doc=(DOMHTMLDocument *)[[webViewBrowser mainFrame] DOMDocument];
		dr=[doc documentElement];
		//[_eset extractPageVariableFrom:dr]
		NSString *t=[doc title];
		if (t) [_eset setHtmlTitle:t];
		NSString *s=[(DOMHTMLElement *)dr innerHTML];
		
		NSLog(@"main frame loaded (%d)\n", [[(DOMHTMLElement *)dr innerHTML]length]);
		[_eset setPageCache:s dom:(DOMHTMLElement *)dr];
		if (webViewExtraction) {
			loadingExtr=YES;
			//[[webViewExtraction mainFrame] loadArchive:[dr webArchive]];
			NSURL *burl=[_eset actualUrl];
			if (s) [[webViewExtraction mainFrame] loadHTMLString:s baseURL:burl];
		} else {
			[_documentRoot release];
			_documentRoot=[dr retain];
			[self didChangeValueForKey:@"documentRoot"];
		}
	} else if ((sender==webViewExtraction) && ([webViewExtraction mainFrame]==frame)) {
		loadingExtr=NO;

		[_documentRoot release];
		_documentRoot=[[[[webViewExtraction mainFrame] DOMDocument] documentElement]retain];
		[self didChangeValueForKey:@"documentRoot"];
		if (loadCallBackTarget) {
			//NSLog(@"call back target %@\n", loadCallBackTarget);
			SEL xact=loadCallBackAction;
			loadCallBackAction=(SEL)0;
			id xtarget=[[loadCallBackTarget retain]autorelease];
			[loadCallBackTarget release];
			loadCallBackTarget=nil;
			id xprivate=[[loadCallBackPrivate retain]autorelease];
			[loadCallBackPrivate release];
			loadCallBackPrivate=nil;
			[xtarget performSelector:xact withObject:self withObject:xprivate];
			
		} else {
			//NSLog(@"no call back\n");
		}
	}
}

- (IBAction) parse:(id) sender
{
	//DOMHTMLElement *e=(DOMHTMLElement *)_documentRoot;
	if (!curitem) return;
	
	DOMRange *s=[webViewExtraction selectedDOMRange];
	DOMNode *ca=[s commonAncestorContainer];

	if (0 && [ca isKindOfClass:[DOMText class]]) {
		ca=[ca parentNode];
	}
	
	NSIndexPath *p=[domCtrl indexPathToObject:ca];
	//[domCtrl 
	[domCtrl setSelectionIndexPath:p];
#if 0
	id c1=[s startContainer];
	id c2=[s endContainer];
	if (c1==c2) {
		NSLog(@"single container %@\n", c1);
		if (ca != c1) {
			NSLog(@"but diffrent ancestor?\n");
		}
	}
#endif
	//id p1=[c parentNode];
	//id p2=[p1 parentNode];
	if (!s) return;
	NSString *mus=[s markupString];
	[[webViewSelection mainFrame] loadHTMLString:mus baseURL:nil];
	if ([ca isKindOfClass:[DOMText class]]) {
		[[webViewElement mainFrame] loadHTMLString:[ca nodeValue] baseURL:[_eset officialUrl]];
	} else if ([ca isKindOfClass:[DOMHTMLElement class]]) {
		NSString *s=[(DOMHTMLElement*)ca outerHTML];
		[[webViewElement mainFrame] loadHTMLString:s baseURL:[_eset officialUrl]];
	}
	[self willChangeValueForKey:@"extractorArray"];
	[[curitem xpath] buildPathFor:ca];
	[self didChangeValueForKey:@"extractorArray"];
	[self updateExtractResult];
#if 0
	if (0) NSLog(@"coucou %@ /%@ [%@]- %@ /%@ - %@ /%@ - %@\n", 
		     [c nodeName], [c nodeValue], [c prefix],
		     [p1 nodeName], [p1 nodeValue],[p2 nodeName], [p2 nodeValue], mus);
#endif
}

- (int) editingMode
{
	return editingMode;
}
- (void) setEditingMode:(int)k
{
	editingMode=k;
}
- (IBAction) browsingDone:(id)sender
{
	[self setEditingMode:1];
}
- (DOMElement *) _documentRoot
{
	//DOMDocument *d=[[webView mainFrame] DOMDocument];
	//DOMElement *e= [d documentElement];
	//return e;
	return nil;
}
- (NSArray *) documentRoot
{
	//id x=[e arrayChildNodes];
	return _documentRoot ? [NSArray arrayWithObject:_documentRoot] : nil;
}
- (NSArray *)extractorArray
{
	if (!curitem) return [NSArray array];
	return [[curitem xpath] extractionPath];
	//return nil;
}


- (NSIndexSet *) selectedExtractionItem
{
	return sei;
}
- (void) setSelectedExtractionItem:(NSIndexSet *)s
{
	int nsel=[s count];
	NSAssert(nsel<=1, @"cant handle several selection");
	if ([s isEqualToIndexSet:sei]) return;
	[sei release];
	sei=[s retain];
	[self curpathUpdated];
}
- (BOOL) haveExtractionItem
{
	if (!sei) return NO;
	if ([sei count]) return YES;
	return NO;
}

- (IBAction) updateJS:(id) sender
{
	[self willChangeValueForKey:@"jsCode"];
	[self didChangeValueForKey:@"jsCode"];
}
- (NSString *) jsCode
{
	return [_eset jsCode];
}

- (WebView *)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request
{
	NSAssert(sender==webViewBrowser, @"createWebViewWithRequest not from webViewBrowser");
	[self performSelector:@selector(browsingRequest:) withObject:[request retain]
		   afterDelay:0.0];
	//return webViewBrowser; // in main window
	return nil;
}

#if 0
- (void)webView:(WebView *)sender mouseDidMoveOverElement:(NSDictionary *)elementInformation modifierFlags:(unsigned int)modifierFlags
{
	if (0) NSLog(@"- (void)webView:(WebView *)sender mouseDidMoveOverElement:(NSDictionary *)elementInformation modifierFlags:(unsigned int)modifierFlags\n");
	return;
}
- (BOOL)webView:(WebView *)sender shouldPerformAction:(SEL)action fromSender:(id)fromObject
{
	NSLog(@"- (BOOL)webView:(WebView *)sender shouldPerformAction:(SEL)action fromSender:(id)fromObject\n");
	return NO;
}
- (BOOL)webView:(WebView *)sender validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item defaultValidation:(BOOL)defaultValidation
{
	NSLog(@"- (BOOL)webView:(WebView *)sender validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item defaultValidation:(BOOL)defaultValidation\n");
	return NO;
}
- (void)webView:(WebView *)sender makeFirstResponder:(NSResponder *)responder
{
}

#endif

- (void)webView:(WebView *)sender decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
        request:(NSURLRequest *)request newFrameName:(NSString *)frameName  decisionListener:(id <WebPolicyDecisionListener>)listener 
{
	NSLog(@"newframe name %@\n",frameName);
	if (sender==webViewBrowser) {
		[self webView:sender decidePolicyForNavigationAction:actionInformation
		      request:request frame:[webViewBrowser mainFrame] decisionListener:listener];
	}
}	
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation
        request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener 
{
	//NSLog(@"nav req %@\n", request);
	NSNumber *act=[actionInformation objectForKey:@"WebActionNavigationTypeKey"];
	id wa=[actionInformation objectForKey:@"WebActionElementKey"];
	if (wa) {
		NSLog(@"got wa=%@\n", wa);
	}
	if (sender==webViewExtraction) {
		//NSString *host = [[request URL] host];
		if (!_documentRoot  || loadingExtr) [listener use];
		else  [listener ignore];
	} else if (sender==webViewElement) {
		[listener use];
		NSNumber *act=[actionInformation objectForKey:@"WebActionNavigationTypeKey"];
		if (WebNavigationTypeLinkClicked==[act intValue]) {
			[self setEditingMode:0];
			WCWebExtractSet *ns=[[WCWebExtractSet alloc]init];
			[ns setSurl:[[request URL] absoluteString]];
			[myDocument addExtractionSet:ns];
			[ns release];
		}
	} else  if (sender==webViewBrowser) {
		// main browser view
		if (!act) {
			NSAssert(0, @"should not occur");
			[listener ignore];
			return;
		}
		int a=[act intValue];
		BOOL mainFrame=([webViewBrowser mainFrame]==frame);
		NSLog(@"nav %d req %@ (mainframe : %s)\n",a, request,  mainFrame ? "YES" : "NO");
		NSString *r=[[request URL]absoluteString];
		NSString *sc=[[request URL] scheme];
		if ([sc isEqualToString:@"applewebdata"]) {
			[listener use];
			return;
		}

		NSString *s1=[[_eset officialUrl] absoluteString];
		NSString *s2=[[_eset actualUrl] absoluteString];
		
		switch (a) {
			case WebNavigationTypeFormSubmitted:
			case WebNavigationTypeLinkClicked:
				// user clicked on link / submit form input
				// do not follow link, instead store it as a new browsing request
				[self performSelector:@selector(browsingRequest:) withObject:[request retain]
					   afterDelay:0.0];
				[listener ignore];
				break;
			case WebNavigationTypeOther:
				if (0) NSLog(@"WebNavigationTypeOther...m=%s r=%@/%@/%@\n", mainFrame?"YES":"NO",
				      r, s1, s2);
#if 0
				if (mainFrame && !([r isEqualToString:s1] || [r isEqualToString:s2])) {
					// eg redirect by refresh or http header
					[self performSelector:@selector(browsingRequest:) withObject:[request retain]
						   afterDelay:0.0];
					[listener ignore];
				} else {
					[listener use];
				}
#else
				[listener use];
#endif
				break;
			default:
				NSAssert(0, @"action not handled\n");
				// FALLTHRU
			case WebNavigationTypeBackForward:
			case WebNavigationTypeReload:
			case WebNavigationTypeFormResubmitted:
				[listener ignore];
				return;
		}
	} else {
		NSAssert(0, @"should not occur\n");
	}
}


// protocol WCWebLoadProtocol

- (void) resetContext
{
}
- (BOOL) loadPageAndPerform:(SEL)sel target:(id)target private:(id)p
{
	//NSLog(@"loadPage, with callback target %@\n", target);
	[loadCallBackTarget release];
	loadCallBackTarget=[target retain];
	loadCallBackAction=sel;
	loadCallBackPrivate=[p retain];
	[self loadPage];
	return YES;
}

- (void) setUrl:(NSURL *)u delegateQuery:(id)dq urlAccess:(WCUrlAccess *)ua bodyVariables:(NSDictionary*)bv
{
	[self setSurl:[u absoluteString]];
}

- (DOMHTMLElement *) rootDOM
{
	return _documentRoot;
}

- (NSAttributedString *) extractionResult
{
	if (!extractionResult) return [[[NSAttributedString  alloc]initWithString:@"(unset)"]autorelease];
	return  [[[NSAttributedString  alloc]initWithString:[extractionResult description]]autorelease];
}
- (void) setExtractionResult:(NSString *)s
{
	[extractionResult release];
	extractionResult=[s retain];
}
- (void) updateExtractResult
{
	NSString *htmlExtract;
	if (!curitem) return;
	NSDictionary *d=[curitem processExtractionWithDom:_documentRoot skipAction:YES fromUrl:[_eset officialUrl]];
	NSAssert([d count]==1, @"bad count in processExtractionWithDom");
	htmlExtract=[d objectForKey:[curitem name]];
	[[webViewResult mainFrame] loadHTMLString:htmlExtract baseURL:[_eset officialUrl]];
	
	d=[curitem processExtractionWithDom:_documentRoot skipAction:NO fromUrl:[_eset officialUrl]];
	NSAssert([d count]==1, @"bad count in processExtractionWithDom");
	[self setExtractionResult: [d objectForKey:[curitem name]]];
	
}

- (void) curpathUpdated
{
	[self willChangeValueForKey:@"possibleActions"];
	[self updateExtractResult];
	[self didChangeValueForKey:@"possibleActions"];
}


- (NSArray *) possibleActions
{
	NSArray *r=getAllSubclassesOf([WCExtractAction class]);
	NSMutableArray *r2=[NSMutableArray arrayWithCapacity:[r count]];
	unsigned int i, count = [r count];
	for (i = 0; i < count; i++) {
		id c = [r objectAtIndex:i];
		NSString *n=[c className];
		WCExtractAction *a=[configuredConverters objectForKey:n];
		[r2 addObject:a?a:c];
	}
	return r2;
}

- (id) selectedAction
{
	if (!configuredConverters) {
		configuredConverters=[[NSMutableDictionary dictionaryWithCapacity:10]retain];
	}
	if (!curitem) return [WCExtractText class];
	if (/*!selectedConverterKey*/![curitem extractAction]) {
		WCExtractText *t=[[WCExtractText alloc]init];
		[curitem setExtractAction:t];
		//selectedConverterKey=[[t className] retain];
		[configuredConverters setObject:t forKey:[t className]];
		[t release];
	}
	NSAssert(curitem, @"curitem not set");
	//WCExtractAction *a=[configuredConverters objectForKey:selectedConverterKey];
	WCExtractAction *a=[curitem extractAction];
	//NSLog(@"selectedAction ? %@\n", a ? a : @"(none)");
	return a;
}
- (void) setSelectedAction:(id)s
{
	BOOL inst=NO;
	if ([s respondsToSelector:@selector(initialize)]) { // is a class
		Class c=(Class)s;
		NSLog(@"instantiate %@\n", s);
		s=[[c alloc]init];
	}
	if ([s isNew]) {
		[s setupGui];
		if ([s respondsToSelector:@selector(setupGuiController:)]) {
			[s setupGuiController:self];
		}
		NSAssert(configuredConverters, @"configuredConverters should be set");
		[self willChangeValueForKey:@"possibleActions"];
		inst=YES;
		[configuredConverters setObject:s forKey:[s className]];
		
	} 
	//[self willChangeValueForKey:@"selectedAction"];
	//[selectedConverterKey release];
	//selectedConverterKey =[[s className]retain];
	//NSLog(@"selectedConverterKey set to %@\n", selectedConverterKey);
	//[self didChangeValueForKey:@"selectedAction"];
	NSAssert(curitem, @"curitem not set");
	[curitem setExtractAction:s];

	if (inst) [self didChangeValueForKey:@"possibleActions"];


	[guiExtractorView setContentView:nil];
	if ([s respondsToSelector:@selector(getView)]) {
		NSView *nsubview=[s getView];
		if (nsubview) {
			[nsubview setHidden:NO];
			[guiExtractorView setContentView:nsubview];
			[nsubview display];
		}
	}
	[guiExtractorView display];
	[self updateExtractResult];
}

@end
